###########################################无自动随机操作，人工操作的同时，自动扫描记录中文字符############################
import os
import time
import pandas as pd
import threading
import queue
import tkinter as tk
from tkinter import ttk, scrolledtext, filedialog, messagebox
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service as ChromeService
from selenium.webdriver.common.by import By
from selenium.common.exceptions import StaleElementReferenceException
from webdriver_manager.chrome import ChromeDriverManager
import re
from datetime import datetime
import hashlib


class ChineseDetector:
    def __init__(self, custom_driver_path=None, log_callback=None):
        # 设置Chrome选项
        chrome_options = Options()
        chrome_options.add_argument("--start-maximized")
        chrome_options.add_argument("--disable-gpu")
        chrome_options.add_argument("--no-sandbox")
        chrome_options.add_argument("--disable-dev-shm-usage")
        chrome_options.add_experimental_option("prefs", {
            "profile.default_content_setting_values.notifications": 2  # 禁用通知
        })

        # 日志回调函数
        self.log_callback = log_callback

        # 使用自定义路径或默认路径
        if custom_driver_path and os.path.exists(custom_driver_path):
            # 使用自定义路径 - 新版Selenium使用Service对象
            service = ChromeService(executable_path=custom_driver_path)
            self.driver = webdriver.Chrome(
                service=service,
                options=chrome_options
            )
            self.log(f"使用自定义Driver路径: {custom_driver_path}", "info")
        else:
            # 使用WebDriverManager自动下载和管理ChromeDriver
            try:
                # 新版WebDriverManager使用方式
                service = ChromeService(ChromeDriverManager().install())
                self.driver = webdriver.Chrome(
                    service=service,
                    options=chrome_options
                )
                self.log("使用WebDriverManager自动下载的ChromeDriver", "info")
            except Exception as e:
                self.log(f"初始化浏览器失败: {e}", "error")
                self.log("请确保已安装Chrome浏览器，或提供正确的ChromeDriver路径", "info")
                # 尝试使用旧版Selenium语法作为备用方案
                try:
                    self.driver = webdriver.Chrome(
                        executable_path=ChromeDriverManager().install(),
                        options=chrome_options
                    )
                    self.log("使用旧版语法成功初始化浏览器", "info")
                except:
                    raise

        # 创建数据存储
        self.all_chinese_elements = []  # 存储所有检测到的元素
        self.recorded_texts = set()  # 存储已记录的文本内容（用于快速去重）
        self.highlighted_elements = set()  # 存储已标记的元素ID
        self.current_url = None  # 当前页面URL
        self.excel_filename = "output/chinese_elements.xlsx"  # 固定的Excel文件名
        self.new_elements_buffer = []  # 新元素缓冲区
        self.last_save_time = time.time()
        self.save_interval = 5  # 每5秒保存一次
        self.is_scanning = False  # 扫描状态标志
        self.scan_count = 0  # 扫描次数计数器
        self.last_position = None  # 记录上次滚动位置
        self.stop_event = threading.Event()  # 停止事件
        self.total_found = 0  # 总共发现的中文文本数量

        # 创建输出目录
        os.makedirs("output", exist_ok=True)

        # 加载已存在的Excel文件（如果存在）
        self.load_existing_data()

        # 常见的弹框选择器
        self.modal_selectors = [
            '[role="dialog"]',
            '.modal',
            '.dialog',
            '.popup',
            '.overlay',
            '.ant-modal',  # Ant Design 弹框
            '.el-dialog',  # Element UI 弹框
            '.MuiDialog-root',  # Material-UI 弹框
            '.v-dialog',  # Vuetify 弹框
            '[class*="modal"]',
            '[class*="dialog"]',
            '[class*="popup"]',
            '[class*="overlay"]'
        ]

    def log(self, message, level="info"):
        """记录日志"""
        if self.log_callback:
            self.log_callback(message, level)
        else:
            print(message)

    def load_existing_data(self):
        """加载已存在的Excel文件数据"""
        if os.path.exists(self.excel_filename):
            try:
                existing_df = pd.read_excel(self.excel_filename)
                for _, row in existing_df.iterrows():
                    # 记录已存在的文本内容
                    self.recorded_texts.add(row['text'])

                self.log(f"已加载 {len(existing_df)} 条现有记录", "info")
            except Exception as e:
                self.log(f"加载现有Excel文件时出错: {e}", "error")

    def find_active_modal(self):
        """查找当前活动的弹框"""
        for selector in self.modal_selectors:
            try:
                modals = self.driver.find_elements(By.CSS_SELECTOR, selector)
                for modal in modals:
                    # 检查弹框是否可见
                    if modal.is_displayed():
                        # 检查弹框是否在视图中（不是隐藏的）
                        if modal.size['width'] > 0 and modal.size['height'] > 0:
                            return modal
            except:
                continue
        return None

    def get_element_id(self, element):
        """获取元素的唯一标识符"""
        try:
            # 尝试使用ID
            element_id = element.get_attribute('id')
            if element_id:
                return f"id:{element_id}"

            # 尝试使用XPath
            xpath = self.get_simple_xpath(element)
            if xpath:
                return f"xpath:{hashlib.md5(xpath.encode('utf-8')).hexdigest()}"

            # 最后使用文本内容
            text = element.text.strip()
            if text:
                return f"text:{hashlib.md5(text.encode('utf-8')).hexdigest()}"

            # 如果都没有，使用位置信息
            location = element.location
            return f"loc:{location['x']}_{location['y']}"
        except:
            return "unknown"

    def is_chinese_text(self, text):
        """检查文本是否包含中文字符"""
        if not text or not isinstance(text, str):
            return False

        # 使用更精确的中文检测
        # 匹配中文字符（包括中文标点符号）
        chinese_pattern = re.compile(r'[\u4e00-\u9fff\u3000-\u303f\uff00-\uffef]')
        return bool(chinese_pattern.search(text))

    def highlight_element(self, element):
        """高亮显示元素（确保标记可见）"""
        try:
            # 使用正确的方式设置!important样式
            self.driver.execute_script(
                "arguments[0].style.setProperty('border', '3px solid red', 'important');" +
                "arguments[0].style.setProperty('box-shadow', '0 0 5px red', 'important');" +
                "arguments[0].style.setProperty('z-index', '9999', 'important');",
                element
            )
            return True
        except StaleElementReferenceException:
            # 元素已过时，忽略
            return False
        except Exception as e:
            self.log(f"高亮元素失败: {e}", "error")
            return False

    def scan_elements(self):
        """扫描元素并高亮中文文本"""
        if self.is_scanning:
            return

        self.is_scanning = True
        self.scan_count += 1
        try:
            # 保存当前滚动位置
            current_scroll_position = self.driver.execute_script("return window.pageYOffset;")

            # 如果页面滚动位置发生变化，重置标记
            if self.last_position is not None and current_scroll_position != self.last_position:
                self.log("检测到页面滚动，重置标记", "info")
                self.highlighted_elements.clear()

            self.last_position = current_scroll_position

            # 扫描所有可见元素（包括下拉框）
            elements = self.driver.find_elements(By.XPATH, "//*[text()]")

            # 每10次扫描打印一次进度
            if self.scan_count % 10 == 0:
                self.log(f"扫描中... 已扫描 {len(elements)} 个元素", "info")

            for element in elements:
                try:
                    # 检查元素是否可见
                    if not element.is_displayed():
                        continue

                    text = element.text.strip()
                    # 只处理包含中文字符的文本
                    if text and self.is_chinese_text(text):
                        # 获取元素唯一标识
                        element_id = self.get_element_id(element)

                        # 检查是否已经标记过
                        if element_id not in self.highlighted_elements:
                            # 添加到已标记元素集合
                            self.highlighted_elements.add(element_id)

                            # 高亮显示包含中文的元素
                            self.highlight_element(element)

                        # 检查文本是否已记录
                        if text not in self.recorded_texts:
                            # 将文本拆分为独立的字符串（按行分割）
                            text_lines = [line.strip() for line in text.split('\n') if line.strip()]

                            for line in text_lines:
                                # 只处理包含中文字符的文本行
                                if self.is_chinese_text(line) and line not in self.recorded_texts:
                                    # 记录元素信息
                                    element_info = {
                                        'text': line,  # 每个独立字符串单独记录
                                        'tag': element.tag_name,
                                        'xpath': self.get_simple_xpath(element),
                                        'page_url': self.driver.current_url,
                                        'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                                    }

                                    # 添加到所有元素列表
                                    self.all_chinese_elements.append(element_info)

                                    # 添加到已记录文本集合
                                    self.recorded_texts.add(line)

                                    # 添加到新元素缓冲区
                                    self.new_elements_buffer.append(element_info)

                                    self.total_found += 1
                                    self.log(f"发现新中文文本 [{self.total_found}]: {line}", "success")
                except StaleElementReferenceException:
                    # 元素已过时，忽略
                    continue
                except Exception as e:
                    # 忽略无法访问的元素
                    continue

            # 检查弹框
            modal = self.find_active_modal()
            if modal:
                # 扫描弹框内容
                modal_elements = modal.find_elements(By.XPATH, ".//*[text()]")
                for element in modal_elements:
                    try:
                        # 检查元素是否可见
                        if not element.is_displayed():
                            continue

                        text = element.text.strip()
                        # 只处理包含中文字符的文本
                        if text and self.is_chinese_text(text):
                            # 获取元素唯一标识
                            element_id = self.get_element_id(element)

                            # 检查是否已经标记过
                            if element_id not in self.highlighted_elements:
                                # 添加到已标记元素集合
                                self.highlighted_elements.add(element_id)

                                # 高亮显示包含中文的元素
                                self.highlight_element(element)

                            # 检查文本是否已记录
                            if text not in self.recorded_texts:
                                # 将文本拆分为独立的字符串（按行分割）
                                text_lines = [line.strip() for line in text.split('\n') if line.strip()]

                                for line in text_lines:
                                    # 只处理包含中文字符的文本行
                                    if self.is_chinese_text(line) and line not in self.recorded_texts:
                                        # 记录元素信息
                                        element_info = {
                                            'text': line,  # 每个独立字符串单独记录
                                            'tag': element.tag_name,
                                            'xpath': self.get_simple_xpath(element),
                                            'page_url': self.driver.current_url,
                                            'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                                            'scope': '弹框'
                                        }

                                        # 添加到所有元素列表
                                        self.all_chinese_elements.append(element_info)

                                        # 添加到已记录文本集合
                                        self.recorded_texts.add(line)

                                        # 添加到新元素缓冲区
                                        self.new_elements_buffer.append(element_info)

                                        self.total_found += 1
                                        self.log(f"在弹框中发现新中文文本 [{self.total_found}]: {line}", "success")
                    except StaleElementReferenceException:
                        # 元素已过时，忽略
                        continue
                    except Exception as e:
                        # 忽略无法访问的元素
                        continue
        finally:
            self.is_scanning = False

    def get_simple_xpath(self, element):
        """获取简化的XPath（提高性能）"""
        try:
            # 首先尝试使用ID
            element_id = element.get_attribute('id')
            if element_id:
                return f"//{element.tag_name}[@id='{element_id}']"

            # 然后尝试使用类名
            class_name = element.get_attribute('class')
            if class_name:
                return f"//{element.tag_name}[@class='{class_name}']"

            # 最后使用基本XPath
            script = """
            function getElementXPath(element) {
                if (element.id !== '')
                    return '//' + element.tagName.toLowerCase() + '[@id="' + element.id + '"]';
                if (element === document.body)
                    return '/html/body';

                var ix = 0;
                var siblings = element.parentNode.childNodes;

                for (var i = 0; i < siblings.length; i++) {
                    var sibling = siblings[i];
                    if (sibling === element)
                        return getElementXPath(element.parentNode) + '/' + element.tagName.toLowerCase() + '[' + (ix + 1) + ']';
                    if (sibling.nodeType === 1 && sibling.tagName === element.tagName)
                        ix++;
                }
            }
            return getElementXPath(arguments[0]);
            """
            return self.driver.execute_script(script, element)
        except:
            return "无法获取XPath"

    def save_to_excel(self, force=False):
        """将检测到的中文元素保存到Excel"""
        current_time = time.time()

        # 检查是否需要保存（有数据且达到保存间隔或强制保存）
        if (self.new_elements_buffer and
                (current_time - self.last_save_time >= self.save_interval or force)):

            # 创建DataFrame
            df = pd.DataFrame(self.new_elements_buffer)

            if os.path.exists(self.excel_filename):
                # 读取现有数据
                try:
                    existing_df = pd.read_excel(self.excel_filename)
                    # 合并数据
                    df = pd.concat([existing_df, df], ignore_index=True)
                except Exception as e:
                    self.log(f"读取现有Excel文件时出错: {e}", "error")

            # 保存到Excel
            try:
                df.to_excel(self.excel_filename, index=False)
                self.log(
                    f"已保存 {len(self.new_elements_buffer)} 条新记录到: {self.excel_filename} (共 {len(df)} 条记录)",
                    "info")
            except Exception as e:
                self.log(f"保存Excel文件时出错: {e}", "error")

            # 清空缓冲区
            self.new_elements_buffer = []
            self.last_save_time = current_time

    def auto_scan_mode(self):
        """自动扫描模式 - 每200ms扫描一次"""
        self.log("启动自动扫描模式...", "info")
        self.log("系统将每200ms扫描一次页面并记录新发现的中文字符", "info")
        self.log(f"结果将保存到: {self.excel_filename}", "info")
        self.log("按停止按钮停止扫描", "info")

        try:
            while not self.stop_event.is_set():
                # 执行扫描
                self.scan_elements()

                # 检查是否需要保存
                self.save_to_excel()

                # 等待200ms
                time.sleep(0.2)

        except Exception as e:
            self.log(f"扫描过程中出错: {e}", "error")
        finally:
            self.log("扫描已停止", "info")
            self.log(f"总计发现 {self.total_found} 条中文文本", "info")

    def close(self):
        """关闭浏览器"""
        # 设置停止事件
        self.stop_event.set()

        # 保存最终结果
        self.save_to_excel(force=True)
        self.log("最终结果已保存", "info")

        if hasattr(self, 'driver'):
            try:
                self.driver.quit()
                self.log("浏览器已关闭", "info")
            except:
                pass


class ChineseScannerApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("中文扫描工具")
        self.geometry("900x700")
        self.protocol("WM_DELETE_WINDOW", self.on_close)

        # 创建UI元素
        self.create_widgets()

        # 扫描器实例
        self.detector = None
        self.scan_thread = None

        # 日志队列
        self.log_queue = queue.Queue()

        # 启动日志更新定时器
        self.after(100, self.update_log)

    def create_widgets(self):
        """创建UI控件"""
        # 创建主框架
        main_frame = ttk.Frame(self, padding=10)
        main_frame.pack(fill=tk.BOTH, expand=True)

        # 标题
        title_frame = ttk.Frame(main_frame)
        title_frame.pack(fill=tk.X, pady=5)

        ttk.Label(title_frame, text="中文扫描工具", font=("Arial", 16, "bold")).pack()
        ttk.Separator(main_frame, orient='horizontal').pack(fill=tk.X, pady=5)

        # 控制面板
        control_frame = ttk.LabelFrame(main_frame, text="控制面板", padding=10)
        control_frame.pack(fill=tk.X, pady=5)

        # 浏览器驱动路径选择
        driver_frame = ttk.Frame(control_frame)
        driver_frame.pack(fill=tk.X, pady=5)

        ttk.Label(driver_frame, text="ChromeDriver路径:").pack(side=tk.LEFT)
        self.driver_path_var = tk.StringVar()
        ttk.Entry(driver_frame, textvariable=self.driver_path_var, width=50).pack(side=tk.LEFT, padx=5)
        ttk.Button(driver_frame, text="浏览...", command=self.browse_driver).pack(side=tk.LEFT)

        # 扫描控制按钮
        button_frame = ttk.Frame(control_frame)
        button_frame.pack(fill=tk.X, pady=10)

        self.start_button = ttk.Button(button_frame, text="开始扫描", command=self.start_scan)
        self.start_button.pack(side=tk.LEFT, padx=5)

        self.stop_button = ttk.Button(button_frame, text="停止扫描", command=self.stop_scan, state=tk.DISABLED)
        self.stop_button.pack(side=tk.LEFT, padx=5)

        # ttk.Button(button_frame, text="打开结果文件", command=self.open_results).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="清除日志", command=self.clear_log).pack(side=tk.LEFT, padx=5)

        # 统计信息
        stats_frame = ttk.Frame(control_frame)
        stats_frame.pack(fill=tk.X, pady=5)

        # 修复：移除 emoji 字符
        # self.stats_var = tk.StringVar(value="就绪 - 共发现 0 条中文文本")
        # ttk.Label(stats_frame, textvariable=self.stats_var, font=("Arial", 10)).pack()

        # 日志区域
        log_frame = ttk.LabelFrame(main_frame, text="日志输出", padding=10)
        log_frame.pack(fill=tk.BOTH, expand=True, pady=5)

        # 创建带滚动条的文本区域
        self.log_text = scrolledtext.ScrolledText(
            log_frame,
            wrap=tk.WORD,
            font=("Consolas", 10),
            bg="#2b2b2b",
            fg="#ffffff",
            insertbackground="white"
        )
        self.log_text.pack(fill=tk.BOTH, expand=True)
        self.log_text.config(state=tk.DISABLED)

        # 配置标签颜色
        self.log_text.tag_config("error", foreground="#ff6b6b")  # 红色
        self.log_text.tag_config("warning", foreground="#ffd93d")  # 黄色
        self.log_text.tag_config("info", foreground="#4ecdc4")  # 青色
        self.log_text.tag_config("success", foreground="#6bcf7f")  # 绿色
        self.log_text.tag_config("highlight", foreground="#ff9ff3")  # 粉色

        # 状态栏
        self.status_var = tk.StringVar(value="就绪")
        status_bar = ttk.Label(self, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
        status_bar.pack(side=tk.BOTTOM, fill=tk.X)

    def browse_driver(self):
        """浏览选择ChromeDriver路径"""
        file_path = filedialog.askopenfilename(
            title="选择ChromeDriver",
            filetypes=[("可执行文件", "*.exe"), ("所有文件", "*.*")]
        )
        if file_path:
            self.driver_path_var.set(file_path)

    def log(self, message, level="info"):
        """记录日志到队列"""
        self.log_queue.put((message, level))

    def update_log(self):
        """更新日志显示"""
        try:
            while True:
                message, level = self.log_queue.get_nowait()
                self.log_text.config(state=tk.NORMAL)

                # 添加时间戳
                timestamp = datetime.now().strftime("[%H:%M:%S] ")
                self.log_text.insert(tk.END, timestamp)

                # 插入消息并应用标签
                self.log_text.insert(tk.END, message + "\n", level)
                self.log_text.see(tk.END)
                self.log_text.config(state=tk.DISABLED)

                # 更新统计信息
                if "共发现" in message and "条中文文本" in message:
                    self.stats_var.set(message)

        except queue.Empty:
            pass

        # 继续定时检查
        self.after(100, self.update_log)

    def clear_log(self):
        """清除日志"""
        self.log_text.config(state=tk.NORMAL)
        self.log_text.delete(1.0, tk.END)
        self.log_text.config(state=tk.DISABLED)

    def start_scan(self):
        """开始扫描"""
        if self.detector and self.detector.is_scanning:
            messagebox.showinfo("提示", "扫描已在运行中")
            return

        # 获取驱动路径
        driver_path = self.driver_path_var.get() or None

        # 创建扫描器实例
        try:
            self.detector = ChineseDetector(driver_path, self.log)
        except Exception as e:
            self.log(f"初始化失败: {e}", "error")
            return

        # 更新UI状态
        self.start_button.config(state=tk.DISABLED)
        self.stop_button.config(state=tk.NORMAL)
        self.status_var.set("扫描运行中...")

        # 启动扫描线程
        self.scan_thread = threading.Thread(target=self.detector.auto_scan_mode, daemon=True)
        self.scan_thread.start()

    def stop_scan(self):
        """停止扫描"""
        if self.detector:
            self.detector.close()
            self.detector = None

        # 更新UI状态
        self.start_button.config(state=tk.NORMAL)
        self.stop_button.config(state=tk.DISABLED)
        self.status_var.set("扫描已停止")


    def on_close(self):
        """关闭应用程序"""
        self.stop_scan()
        self.destroy()


if __name__ == "__main__":
    app = ChineseScannerApp()
    app.mainloop()